package util.rsa;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import sun.misc.BASE64Decoder;

/**
 * Title: Description: RSA工具类
 *
 * @author chenz
 * @version 1.0
 */
public class RSAUtil {

    /**
     * 本地测试路径
     */
    public static final String PUB_XD = "C:\\M\\IDEAWorkspace\\Project\\Dev\\J2EE\\ysxd\\ysxd-loan\\src\\main\\resources\\key\\public_key.cer"; //小贷公司公钥
    public static final String PRI_XD = "C:\\M\\IDEAWorkspace\\Project\\Dev\\J2EE\\ysxd\\ysxd-loan\\src\\main\\resources\\key\\private_key.pem"; //小贷公司私钥
    public static final String PUB_SHZX = "C:\\M\\IDEAWorkspace\\Project\\Dev\\J2EE\\ysxd\\ysxd-loan\\src\\main\\resources\\key\\public.cer";  //上海资信公钥
    public static final String PRI_SHZX = "/"; //上海资信私钥


    private static BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();


    // 单例模式
    public static RSAUtil instance;

    public static RSAUtil instance() {
        try {
            if (instance == null) {
                instance = new RSAUtil();
            }
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 从文件获取公钥
     *
     * @param strFile
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws CertificateException
     */
    public static RSAPublicKey getRsaPubKeyByFile(String strFile) throws NoSuchAlgorithmException, InvalidKeySpecException, CertificateException {
        InputStream fin = null;
        PublicKey pk = null;
        try {
            fin = new FileInputStream(new File(strFile));

            CertificateFactory f = CertificateFactory.getInstance("X.509");

            X509Certificate certificate = (X509Certificate) f

                    .generateCertificate(fin);

            pk = certificate.getPublicKey();
        } catch (Exception e) {
            new RuntimeException("粤商小贷文件流异常！", e);
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    new RuntimeException("粤商小贷文件流关闭异常！", e);
                }
            }

        }
        return (RSAPublicKey) pk;

    }

    /**
     * 从字符串获取公钥
     *
     * @param str
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws CertificateException
     */
    public static RSAPublicKey getRsaPubKeyByString(String str) {
        InputStream fin = null;
        PublicKey pk = null;
        try {
            fin = new ByteArrayInputStream(str.toString().getBytes());

            CertificateFactory f = CertificateFactory.getInstance("X.509");

            X509Certificate certificate = (X509Certificate) f

                    .generateCertificate(fin);

            pk = certificate.getPublicKey();
        } catch (Exception e) {
            new RuntimeException("粤商小贷文件流异常！", e);
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    new RuntimeException("粤商小贷文件流关闭异常！", e);
                }
            }

        }
        return (RSAPublicKey) pk;
    }

    /**
     * 从文件获取私钥
     *
     * @param strFile
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static RSAPrivateKey getRsaPriKeyByFile(String strFile)
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {
        InputStream in_private = null;
        RSAPrivateKey pk = null;
        try {
            in_private = new FileInputStream(new File(strFile));
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(in_private));
            String readLine = null;
            StringBuffer sb = new StringBuffer();
            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) == '-') {
                    continue;
                } else {
                    sb.append(readLine);
                    sb.append('\r');
                }
            }
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(sb.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            pk = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            new RuntimeException("粤商小贷文件流异常！", e);
        } finally {
            if (in_private != null) {
                try {
                    in_private.close();
                } catch (IOException e) {
                    new RuntimeException("粤商小贷文件流关闭异常！", e);
                }
            }

        }
        return pk;
    }

    /**
     * 从字符串获取私钥
     *
     * @param str
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static RSAPrivateKey getRsaPriKeyByString(String str)
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {
        InputStream in_private = null;
        RSAPrivateKey pk = null;
        try {
            in_private = new ByteArrayInputStream(str.getBytes());
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(in_private));
            String readLine = null;
            StringBuffer sb = new StringBuffer();
            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) == '-') {
                    continue;
                } else {
                    sb.append(readLine);
                    sb.append('\r');
                }
            }
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(sb.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            pk = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            new RuntimeException("粤商小贷文件流异常！", e);
        } finally {
            if (in_private != null) {
                try {
                    in_private.close();
                } catch (IOException e) {
                    new RuntimeException("粤商小贷文件流关闭异常！", e);
                }
            }

        }
        return pk;
    }


    /**
     * 私钥加密过程
     *
     * @param privateKey    私钥
     * @param plainTextData 明文数据
     * @return
     * @throws Exception 加密过程中的异常信息
     */
    private static byte[] encrypt(RSAPrivateKey privateKey, byte[] plainTextData)
            throws Exception {
        if (privateKey == null) {
            throw new Exception("加密私钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("RSA", bouncyCastleProvider);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] output = cipher.doFinal(plainTextData);
            return output;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此加密算法");
        } catch (NoSuchPaddingException e) {
            return null;
        } catch (InvalidKeyException e) {
            throw new Exception("加密私钥非法,请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("明文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("明文数据已损坏");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 公钥解密过程
     *
     * @param publicKey  公钥
     * @param cipherData 密文数据
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     */
    private static byte[] decrypt(RSAPublicKey publicKey, byte[] cipherData)
            throws Exception {
        if (publicKey == null) {
            throw new Exception("解密公钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("RSA", bouncyCastleProvider);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] output = cipher.doFinal(cipherData);
            return output;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此解密算法");
        } catch (NoSuchPaddingException e) {
            return null;
        } catch (InvalidKeyException e) {
            throw new Exception("解密公钥非法,请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("密文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("密文数据已损坏");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 字节数据转十六进制字符串
     */
    private static String bytesToHexString(byte[] src) {
        StringBuffer stringBuilder = new StringBuffer("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * 十六进制字符串转字节数据
     */
    private static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        // hexString = hexString.toUpperCase(); //大写转换
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    private static byte charToByte(char c) {
        return (byte) "0123456789abcdef".indexOf(c);
    }

    /**
     * 字符串私钥加密
     *
     * @param prikey
     * @param src
     * @return
     */
    public static String encString(RSAPrivateKey prikey, String src) {
        String encStr = "";
        try {
            byte[] encryptByte = encrypt(prikey, src.getBytes("UTF8"));
            encStr = bytesToHexString(encryptByte);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encStr;
    }

    /**
     * 字符串公钥加密
     *
     * @param pubKey
     * @param src
     * @return
     */
    public static String encString(RSAPublicKey pubKey, String src) {
        String encStr = "";
        try {
            byte[] encryptByte = encrypt(pubKey, src.getBytes("UTF8"));
            encStr = bytesToHexString(encryptByte);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encStr;
    }


    /**
     * 公钥加密过程
     *
     * @param publicKey     公钥
     * @param plainTextData 明文数据
     * @return
     * @throws Exception 加密过程中的异常信息
     */
    private static byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData)
            throws Exception {
        if (publicKey == null) {
            throw new Exception("加密公钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("RSA", bouncyCastleProvider);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] output = cipher.doFinal(plainTextData);
            return output;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此加密算法");
        } catch (NoSuchPaddingException e) {
            return null;
        } catch (InvalidKeyException e) {
            throw new Exception("加密公钥非法,请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("明文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("明文数据已损坏");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 私钥解密过程
     *
     * @param privateKey 私钥
     * @param cipherData 密文数据
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     */
    private static byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData)
            throws Exception {
        if (privateKey == null) {
            throw new Exception("解密私钥为空, 请设置");
        }
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("RSA", bouncyCastleProvider);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] output = cipher.doFinal(cipherData);
            return output;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此解密算法");
        } catch (NoSuchPaddingException e) {
            return null;
        } catch (InvalidKeyException e) {
            throw new Exception("解密私钥非法,请检查");
        } catch (IllegalBlockSizeException e) {
            throw new Exception("密文长度非法");
        } catch (BadPaddingException e) {
            throw new Exception("密文数据已损坏");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 字符串公钥解密
     *
     * @param enc
     * @return
     */
    public static String decString(RSAPublicKey publicKey, String enc) {
        String dec = "";
        try {
            RSAUtil rsaEncryptUtils = new RSAUtil();
            // 传输密文
            byte[] unDecryptByte = hexStringToBytes(enc);
            byte[] decryptByte = rsaEncryptUtils.decrypt(publicKey, unDecryptByte);
            dec = new String(decryptByte, "UTF8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dec;
    }

    /**
     * 字符串私钥解密
     *
     * @param priKey
     * @param enc
     * @return
     */
    public static String decString(RSAPrivateKey priKey, String enc) {
        String dec = "";
        try {
            // 传输密文
            byte[] unDecryptByte = hexStringToBytes(enc);
            byte[] decryptByte = RSAUtil.decrypt(priKey, unDecryptByte);
            dec = new String(decryptByte, "UTF8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dec;
    }


    /**
     * 签名
     *
     * @param plainText
     * @param prikey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static String signString(String plainText, RSAPrivateKey prikey)
            throws NoSuchAlgorithmException, InvalidKeyException,
            SignatureException {
        String signStr = "";
        // 用私钥对信息生成数字签名
        java.security.Signature signet = java.security.Signature
                .getInstance("MD5withRSA");
        signet.initSign(prikey);
        signet.update(plainText.getBytes());
        signStr = bytesToHexString(signet.sign());
        return signStr;
    }

    /**
     * 验签
     *
     * @param info
     * @param signed
     * @param pubKey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static boolean vsign(String info, String signed, RSAPublicKey pubKey)
            throws NoSuchAlgorithmException, InvalidKeyException,
            SignatureException {
        java.security.Signature signetcheck = java.security.Signature
                .getInstance("MD5withRSA");
        signetcheck.initVerify(pubKey);
        signetcheck.update(info.getBytes());

        if (signetcheck.verify(hexStringToBytes(signed))) {
            // System.out.println("原始信息：" + info);
            // System.out.println("签名信息：" + signed);
            // System.out.println("签名正常");
            return true;
        } else {
            System.out.println("非签名正常");
            return false;
        }
    }


//	/**
//	 * 加密
//	 *
//	 * @throws IOException
//	 * @throws InvalidKeySpecException
//	 * @throws SignatureException
//	 * @throws NoSuchAlgorithmException
//	 * @throws InvalidKeyException
//	 * @throws CertificateException
//	 */
//	public static void main(String[] args) throws IOException,
//			InvalidKeyException, NoSuchAlgorithmException, SignatureException,
//			InvalidKeySpecException, CertificateException {
//		// ===============================================
//		RSAPublicKey xdpubkey = getRsaPubKeyByFile(PUB_XD);//小贷公司公钥
//		RSAPrivateKey xdPrikey = getRsaPriKeyByFile(PRI_XD);//小贷公司私钥
//
//		RSAPublicKey shzxpubkey = getRsaPubKeyByFile(PUB_SHZX);//上海资信公钥
////		RSAPrivateKey  shzxPrikey = getRsaPriKeyByFile(PRI_SHZX);//上海资信私钥
//		// ==================加密=============================
//		String data = "王五610303199901010101xxxxxxxxxxxxx09090020150330";
//		System.out.println("原始数据：" + data);
//
//		// step 1: 原始数据用上海资信公钥加密
//		String encstr = encString(shzxpubkey, data);
//		System.out.println("上海资信加密数据：" + encstr);
//
//		// step 2: 小贷公司用自己的私钥对用上海资信公钥加密的数据进行签名
//		String signStr = signString(encstr, xdPrikey);
//		System.out.println("小贷公司签名数据："+signStr);
//
//		// step 3: 上海资信用小贷公司的公钥对小贷公司签名的数据进行验签
//		boolean vSign = vsign(encstr, signStr, xdpubkey);
//		System.out.println("上海资信验签结果："+vSign);
//
////		// step 4: 上海资信用自己的私钥对小贷公司加密的数据进行解密
////		String decStr = decString(shzxPrikey, encstr);
////		System.out.println("上海资信解密内容："+decStr);
//
//	}
}